using System;
using System.Collections.Generic;
using System.Reflection;

namespace needle.Services.astra.Common
{
	/// <summary>
	/// Helper class for identifying item storability.
	/// </summary>
	/// <typeparam name="T">Item type.</typeparam>
	public static class StorageHelper<T>
	{
		/// <summary>
		/// Helper class for <see cref="StorageHelper{T}.SkipStorage"/>.
		/// </summary>
		private class SkipStorageHelper<T> : IDisposable
		{
			/// <summary>
			/// Constructor.
			/// Sets <see cref="StorageHelper{T}._skipStorageHelper"/> to this.
			/// </summary>
			public SkipStorageHelper()
			{
				StorageHelper<T>._skipStorageHelper = this;
			}

			#region IDisposable Members

			/// <summary>
			/// Sets <see cref="StorageHelper{T}._skipStorageHelper"/> to null.
			/// </summary>
			public void Dispose()
			{
				StorageHelper<T>._skipStorageHelper = null;
			}

			#endregion
		}


		/// <summary>
		/// Constructor.
		/// </summary>
		/// <remarks>Calls <see cref="FillStorableProperties"/>.</remarks>
		static StorageHelper()
		{
			_storableAttribute = typeof(StorableAttribute);
			StorableProperties = new List<string>();
			FillStorableProperties();
		}

		/// <summary>
		/// Names of properties of <typeparamref name="T"/> which hold essential item information which needs to be stored for later reinstantiation of an item.
		/// </summary>
		/// <value>Default: Filled by <see cref="FillStorableProperties"/>.</value>
		public static readonly IList<string> StorableProperties;
		/// <summary>
		/// Filter identifying whether an item`s property is storable.
		/// </summary>
		/// <value>Default: null.</value>
		public static ItemPropertyIsStorableDelegate<T> ItemPropertyIsStorableFilter;
		/// <summary>
		/// Attribute used to identify storable properties.
		/// </summary>
		/// <value>
		/// When changed, <see cref="FillStorableProperties"/> is called.
		/// Default: typeof(<see cref="StorableAttribute"/>).
		/// </value>
		/// <exception cref="ArgumentNullException">set: Value is null.</exception>
		/// <exception cref="ArgumentException">set: Value is not derived from <see cref="Attribute"/>.</exception>
		public static Type StorableAttribute
		{
			get { return _storableAttribute; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("StorableAttribute");
				if (!value.IsSubclassOf(typeof(Attribute)))
					throw new ArgumentException("Must be derived from Attribute.", "StorableAttribute");
				if (_storableAttribute == value)
					return;
				_storableAttribute = value;
				FillStorableProperties();
			}
		}
		/// <summary>
		/// Whether <see cref="IsPropertyStorable"/> returns always false.
		/// </summary>
		public static bool IsSkipStorage
		{
			get { return _skipStorageHelper != null; }
		}

		/// <summary>
		/// Fills <see cref="StorableProperties"/>.
		/// </summary>
		/// <remarks>
		/// Adds all properties of <typeparamref name="T"/> which have <see cref="StorableAttribute"/> applied.
		/// </remarks>
		public static void FillStorableProperties()
		{
			StorableProperties.Clear();
			foreach (PropertyInfo property in typeof(T).GetProperties())
			{
				if (property.IsDefined(StorableAttribute, true))
					StorableProperties.Add(property.Name);
			}
		}
		/// <summary>
		/// Whether an <paramref name="item"/>`s property is storable.
		/// </summary>
		/// <param name="propertyName">Property name.</param>
		/// <param name="trueIfNoStorablePropertyExists">Used when <see cref="StorableProperties"/> is empty.</param>
		/// <returns>
		/// If <see cref="IsSkipStorage"/> is true, returns false.
		/// true if <paramref name="trueIfNoStorablePropertyExists"/> is true and <see cref="StorableProperties"/> is empty.
		/// true if <paramref name="propertyName"/> is in <see cref="StorableProperties"/>.
		/// </returns>
		public static bool IsPropertyStorable(string propertyName, bool trueIfNoStorablePropertyExists)
		{
			if (IsSkipStorage)
				return false;
			else
				return
					trueIfNoStorablePropertyExists && StorableProperties.Count == 0 ||
					StorableProperties.Contains(propertyName);
		}
		/// <summary>
		/// Calls <see cref="IsPropertyStorable(string, bool)"/> with true for trueIfNoStorablePropertyExists parameter.
		/// </summary>
		public static bool IsPropertyStorable(string propertyName)
		{
			return IsPropertyStorable(propertyName, true);
		}
		/// <summary>
		/// Whether an <paramref name="item"/>`s property is storable.
		/// </summary>
		/// <param name="item">Item.</param>
		/// <param name="propertyName">Property name.</param>
		/// <param name="trueIfNoStorablePropertyExists">See <see cref="IsPropertyStorable(string, bool)"/>.</param>
		/// <returns>
		/// If <see cref="IsSkipStorage"/> is true, returns false.
		/// If <see cref="ItemPropertyIsStorableFilter"/> is not null returns its result, otherwise returns result of <see cref="IsPropertyStorable(string, bool)"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception>
		public static bool IsPropertyStorable(T item, string propertyName, bool trueIfNoStorablePropertyExists)
		{
			if (IsSkipStorage)
				return false;
			if (item == null)
				throw new ArgumentNullException("item");
			if (ItemPropertyIsStorableFilter != null)
				return ItemPropertyIsStorableFilter(item, propertyName);
			else
				return IsPropertyStorable(propertyName, trueIfNoStorablePropertyExists);
		}
		/// <summary>
		/// Calls <see cref="IsPropertyStorable(T, string, bool)"/> with true for trueIfNoStorablePropertyExists parameter.
		/// </summary>
		public static bool IsPropertyStorable(T item, string propertyName)
		{
			return IsPropertyStorable(item, propertyName, true);
		}
		/// <summary>
		/// Turns on <see cref="IsSkipStorage"/> and turns it off when the returned object is disposed.
		/// </summary>
		/// <returns>Disposable switch.</returns>
		public static IDisposable SkipStorage()
		{
			return new SkipStorageHelper<T>();
		}

		/// <summary>
		/// See <see cref="StorableAttribute"/>.
		/// </summary>
		private static Type _storableAttribute;
		/// <summary>
		/// See <see cref="IsSkipStorage"/>.
		/// </summary>
		private static IDisposable _skipStorageHelper;
	}


	/// <summary>
	/// Used by <see cref="StorageHelper{T}.ItemPropertyIsStorableFilter"/>.
	/// </summary>
	/// <typeparam name="T">Item type.</typeparam>
	/// <param name="item">Item.</param>
	/// <param name="propertyName">Property name.</param>
	/// <returns>
	/// Whether item`s property is storable.
	/// </returns>
	public delegate bool ItemPropertyIsStorableDelegate<T>(T item, string propertyName);


	/// <summary>
	/// Specifies whether a property must be stored to allow later item reinstantiation.
	/// </summary>
	[AttributeUsage(AttributeTargets.Property)]
	public class StorableAttribute : Attribute
	{
	}
}
